<?php
//
// +----------------------------------------------------------------------+
// | No Direct Links!                                                     |
// +----------------------------------------------------------------------+
// | Copyright (c) 2002-2004 Hayk Chamyan <hayk@mail.ru>                  |
// +----------------------------------------------------------------------+
// | This program is free software; you can redistribute it and/or modify |
// | it  under  the  terms  of  the  GNU Lesser General Public License as |
// | published by the Free Software Foundation; either version 2.1 of the |
// | License, or (at your option) any later version.                      |
// |                                                                      |
// | This  program is distributed in the hope that it will be useful, but |
// | WITHOUT   ANY  WARRANTY;  without  even  the  implied  warranty  of  |
// | MERCHANTABILITY  or  FITNESS  FOR A PARTICULAR PURPOSE.  See the GNU |
// | Lesser General Public License for more details.                      |
// +----------------------------------------------------------------------+
//
// $Id: ndl.class.php 10 2013-02-16 08:44:37Z hamshen@gmail.com $
//

/**
 * No Direct Links!
 *
 * NDL is a class that gives you total control over the process of
 * downloading files from your server by hiding the actual file location.
 * Using different rules (based on IP, browser or download manager, http
 * referrer, number of simultaneous connections, authorization status) you
 * can grant or deny permission to download files.
 *
 * Besides, you can also gather statistics on the following:
 *  - download manager
 *  - http referrer
 *  - downloaded files size and more...
 *
 *
 * @author		Hayk Chamyan <hayk@mail.ru>
 * @copyright	(c) 2002-2004 Hayk Chamyan <hayk@mail.ru>
 * @link		http://phpclasses.org
 * @version		0.7.1
 * @access		public
 */

error_reporting(0);
set_time_limit(0);

/**
 *
 *
 * @const	DEFAULT_STORAGE
 * @access	public
 */
define('DEFAULT_STORAGE', './st/');
/**
 *
 *
 * @const	DEFAULT_BUF_SIZE
 * @access	public
 */
define('DEFAULT_BUF_SIZE', 8192);
/**
 * Display directly
 *
 * @const	CD_DISPLAY
 * @access	public
 */
define('CD_DISPLAY', 'inline');
/**
 * Save to disk
 *
 * @const	CD_SAVE
 * @access	public
 */
define('CD_SAVE', 'attachment');

/**
 *
 *
 * @const	CT_APP_OS
 * @access	public
 */
define('CT_APP_OS', 'application/octet-stream');

/**
 * IE, Opera
 *
 * @const	CT_APP_OS_IE
 * @access	public
 */
define('CT_APP_OS_IE', 'application/octetstream');
/**
 * NN & ...
 *
 * @const	CT_APP_OS_NN
 * @access	public
 */
define('CT_APP_OS_NN', 'application/octet-stream');

/**
 *
 *
 * @const	HDR_X_SCRIPT
 * @access	public
 */
define('HDR_X_SCRIPT', 'X-Script: No Direct Links! v0.7.1');
/**
 *
 *
 * @const	CON_STATUS_NORMAL
 * @access	private
 */
define('CON_STATUS_NORMAL', CONNECTION_NORMAL);

define('MSG_404', '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">'.PHP_EOL.'<HTML><HEAD>'.PHP_EOL.'<TITLE>404 Not Found</TITLE>'.PHP_EOL.'</HEAD><BODY>'.PHP_EOL.'<H1>Not Found</H1>'.PHP_EOL.'The requested URL %s was not found on this server.<P>'.PHP_EOL.'<HR>'.PHP_EOL.'%s'.'</BODY></HTML>'.PHP_EOL);
define('MSG_403', '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">'.PHP_EOL.'<HTML><HEAD>'.PHP_EOL.'<TITLE>403 Forbidden</TITLE>'.PHP_EOL.'</HEAD><BODY>'.PHP_EOL.'<H1>Forbidden</H1>'.PHP_EOL.'You don\'t have permission to access %s '.PHP_EOL.'on this server.<P>'.PHP_EOL.'<HR>'.PHP_EOL.'%s'.'</BODY></HTML>'.PHP_EOL);

define('HTTP_200', 200);
define('HTTP_206', 206);
define('HTTP_403', 403);
define('HTTP_404', 404);

/**
 * NDL - No Direct Links!
 *
 * @package		NDL
 * @author		hayk@mail.ru
 * @copyright	(c) 2002 hayk@mail.ru
 * @access		public
 */
class NDL
{

	/**
	 *
	 *
	 * @var
	 */
	var $vars;

	/**
	 *
	 *
	 * @var
	 */
	var $server;

	/**
	 *
	 *
	 * @var
	 */
	var $fileName;

	/**
	 *
	 *
	 * @var
	 */
	var $fileTime;

	/**
	 *
	 *
	 * @var
	 */
	var $storedFile;

	/**
	 *
	 *
	 * @var
	 */
	var $contentSize;

	/**
	 *
	 *
	 * @var
	 */
	var $storedFileSize;

	/**
	 *
	 *
	 * @var
	 */
	var $httpContentDisposition;

	/**
	 *
	 *
	 * @var
	 */
	var $httpContentDescription;

	/**
	 *
	 *
	 * @var
	 */
	var $httpContentType;

	/**
	 *
	 *
	 * @var
	 */
	var $bufSize;

	/**
	 *
	 *
	 * @var
	 */
	var $useFpassthru;

	/**
	 * NDL class constructor.
	 *
	 * @param 	$file	string
	 * @param 	$storage	string
	 * @param 	$description	string
	 * @param 	$type	integer
	 * @param 	$content	string
	 * @access	public
	 * @final
	 */
	function NDL($file, $fileName=null, $description=null, $type=CD_SAVE, $content=CT_APP_OS)
	{
		$this->bufSize = DEFAULT_BUF_SIZE;
		if ($fileName)
		{
			$this->fileName = $fileName;
		}
		else
		{
			$this->fileName = array_pop(explode('/', $file));
		}
		$this->storedFile = $file;
		$this->httpContentType = $content;
		$this->httpContentDisposition = $type;
		$this->httpContentDescription = $description;
		$this->useFpassthru = false;

		$this->fileTime = null;

		if (isset($HTTP_GET_VARS))
		{ $this->vars = array_merge($HTTP_GET_VARS, $HTTP_POST_VARS, $HTTP_COOKIE_VARS, $HTTP_POST_FILES); }
		else
		{ $this->vars = &$_REQUEST; }

		if (isset($_SERVER))
		{ $this->server = &$_SERVER; }
		else
		{ $this->server = &$GLOBALS['HTTP_SERVER_VARS']; }

	} // end function NDL

	/**
	 * NDL class destructor.
	 *
	 * @access	public
	 * @final
	 */
	function _NDL()
	{

	} // end function _NDL

	/**
	 *
	 *
	 * @access	public
	 * @final
	 */
	function send()
	{

		if ( !$this->isAllowed() )
		{
			$this->updateStat(HTTP_403);
			$this->http403();
		}
		elseif ( (!isset($this->storedFile)) || empty($this->storedFile) || (! file_exists( $this->storedFile)) )
		{
			$this->updateStat(HTTP_404);
			$this->http404();
		}
		else
		{
			if (!$this->fileTime)
			{
				$this->fileTime = filemtime( $this->storedFile);
			}
			$this->storedFileSize = filesize( $this->storedFile);
			$fd = fopen($this->storedFile, 'rb');
			if ( isset($this->server['HTTP_RANGE']) )
			{
				preg_match('/bytes=(\d+)-/', $this->server['HTTP_RANGE'], $m);
				$offset = intval($m[1]);
				$this->contentSize = $this->storedFileSize - $offset;
				fseek($fd, $offset);
				$this->updateStat(HTTP_206);
				$this->http206();
			}
			else
			{
				$this->contentSize = $this->storedFileSize;
				$this->updateStat(HTTP_200);
				$this->http200();
			}

			if ( $this->useFpassthru && (connection_status() == CON_STATUS_NORMAL) )
			{
				fpassthru($fd);
			}
			else
			{
				while ( !feof($fd) && (connection_status() == CON_STATUS_NORMAL) )
				{
					$contents = fread($fd, $this->bufSize);
					echo $contents;
					ob_flush();
				}
			}
			fclose($fd);
		}
	} // end function send

	/**
	 *
	 *
	 * @access	private
	 * @final
	 */
	function http200()
	{
		header('HTTP/1.1 200 OK');
		header('Date: ' . $this->getGMTDateTime());
		header('X-Powered-By: PHP/' . phpversion());
		header(HDR_X_SCRIPT);
		header('Expires: Thu, 19 Nov 1981 08:52:00 GMT');
		header('Last-Modified: ' . $this->getGMTDateTime($this->fileTime) );
		//header('Cache-Control: None');
		//header('Pragma: public');
		// -   IE
		//header('Cache-Control: no-store, no-cache, must-revalidate');
		//header('Cache-Control: post-check=0, pre-check=0', false);
		header('Cache-Control: None');
		header('Pragma: no-cache');
		//header('Content-Transfer-Encoding: none');
		//header('Content-Transfer-Encoding: binary');
		header('Accept-Ranges: bytes');
		header('Content-Disposition: ' . $this->httpContentDisposition . '; filename="' . $this->fileName . '"');
		header('Content-Type: ' . $this->httpContentType);
		//header('Content-Type: ' . $this->httpContentType . '; name="' . $this->fileName . '"');
		//header('Content-Type: application/octet-stream');
		//header('Content-Type: application/force-download');
		//header('Content-Type: application/download');
		if ($this->httpContentDescription)
			header('Content-Description: ' . $this->httpContentDescription );
		header('Content-Length: ' . $this->contentSize);
		header('Proxy-Connection: close');
		header('');
	} // end function http200

	/**
	 *
	 *
	 * @access	private
	 * @final
	 */
	function http206()
	{
		$p1 = $this->storedFileSize - $this->contentSize;
		$p2 = $this->storedFileSize - 1;
		$p3 = $this->storedFileSize;

		header('HTTP/1.1 206 Partial Content');
		header('Date: ' . $this->getGMTDateTime());
		header('X-Powered-By: PHP/' . phpversion());
		header(HDR_X_SCRIPT);
		header('Expires: Thu, 19 Nov 1981 08:52:00 GMT');
		header('Last-Modified: ' . $this->getGMTDateTime($this->fileTime) );
		//header('Cache-Control: None');
		//header('Pragma: public');
		// -   IE
		//header('Cache-Control: no-store, no-cache, must-revalidate');
		//header('Cache-Control: post-check=0, pre-check=0', false);
		header('Cache-Control: None');
		header('Pragma: no-cache');
		//header('Content-Transfer-Encoding: none');
		//header('Content-Transfer-Encoding: binary');
		header('Accept-Ranges: bytes');
		header('Content-Disposition: ' . $this->httpContentDisposition . '; filename="' . $this->fileName . '"');
		header('Content-Type: ' . $this->httpContentType);
		//header('Content-Type: ' . $this->httpContentType . '; name="' . $this->fileName . '"');
		//header('Content-Type: application/octet-stream');
		//header('Content-Type: application/force-download');
		//header('Content-Type: application/download');
		if ($this->httpContentDescription)
			header('Content-Description: ' . $this->httpContentDescription );
		header('Content-Range: bytes ' . $p1 . '-' . $p2 . '/' . $p3);
		header('Content-Length: ' . $this->contentSize);
		header('Proxy-Connection: close');
		header('');
	} // end function http206

	/**
	 *
	 *
	 * @access	private
	 * @final
	 */
	function http404()
	{
		header('HTTP/1.1 404 Object Not Found');
		header('X-Powered-By: PHP/' . phpversion());
		header(HDR_X_SCRIPT);
		header('');
		echo sprintf(MSG_404, $this->server['REQUEST_URI'], $this->server['SERVER_SIGNATURE']);
	} // end function http404

	/**
	 *
	 *
	 * @access	private
	 * @final
	 */
	function http403()
	{
		header('HTTP/1.1 403 Forbidden');
		header('X-Powered-By: PHP/' . phpversion());
		header(HDR_X_SCRIPT);
		header('');
		echo sprintf(MSG_403, $this->server['REQUEST_URI'], $this->server['SERVER_SIGNATURE']);
	} // end function http403

	/**
	 *
	 * @param	int		$time	UNIX timestamp
	 * @return	string	GMT formated time
	 * @access	public
	 * @final
	 */
	function getGMTDateTime($time=null)
	{
		$offset = date('O');
		$roffset = '';
		if ($offset[0] == '+')
		{
			$roffset = '-';
		}
		else
		{
			$roffset = '+';
		}
		$roffset .= $offset[1].$offset[2];
		if (!$time)
		{
			$time = Time();
		}
		return ( date('D, d M Y H:i:s', $time+$roffset*3600) . ' GMT' );
	} // end function getGMTDateTime

	/**
	 *
	 *
	 * @return	bool
	 * @access	public
	 * @abstract
	 */
	function isAllowed()
	{
		return true;
	} // end function isAllowed

	/**
	 *
	 * @param	string	$code HTTP code
	 * @access	public
	 * @abstract
	 */
	function updateStat($code)
	{
		return true;
	} // end function updateStat

} // end class NDL
